Introduction to Menus

The Win32 API provides a number of functions to create menus dynamically from scratch, but the preferred way is to create menu templates and store them as resources in the EXE file. This is usually accomplished with a menu editor and a resource compiler, but here we show how to create a menu template without these special tools.

[Download source code.]

Adding a menu to a window

Our program, winmenu.asm, will define the menu template in memory. Then it will create a menu "object" by calling LoadMenuIndirect, which returns a menu handle. The menu can then be added at window creation time by setting the menu handle in the appropriate CreateWindowEx argument.
    You can also create a window without a menu and later add it with SetMenu. This API function can also be used to change menus. Menus can also be deleted with this API function by using a NULL (zero) menu handle.
    Menus can be further managed and manipulated via their handles.
        extrn   LoadMenuIndirect:near

        .code
        ; Place this code in InitApp
        ; ESI points to CreateWindowEx argument list
        push    offset appMenuTemplate
        call    LoadMenuIndirect
        mov     [esi].cwargMenu,eax    ; add menu at creation time

DEFAULT_STYLE   equ WS_VISIBLE + WS_OVERLAPPEDWINDOW
DEFAULT_EXSTYLE equ WS_EX_WINDOWEDGE + WS_EX_CLIENTEDGE
DEFAULT_X               equ 100
DEFAULT_Y               equ 100
DEFAULT_WIDTH   equ 400
DEFAULT_HEIGHT  equ 200

        .data
        align   4
cwargs CREATEARGS <DEFAULT_EXSTYLE,wndclsname,def_title,DEFAULT_STYLE, DEFAULT_X,DEFAULT_Y, DEFAULT_WIDTH,DEFAULT_HEIGHT, 0,0, 0, 0>
def_title db 'Window with menu',0

Menu template definition

The first two template entries are part of the template header. The two entries are the menu template version number, and an offset to the list of menu items. The offset allows the skipping of additional header information. Version 0 is the original NT and older Win16 template. We will be using the new version 1 template. It adds the F1 help ID to the header. Note that the offset entry in the header is set to skip this extra information. The header must be aligned on a DWORD boundary.
    Each menu item that follows the header must also be aligned on a DWORD boundary. There are five fields (entries) and a sixth field which is supplied only for a popup menu item. They are, in order: the item type, the item state, the item ID, menu structure info, the item text, and the F1 help ID.
    The item type field contains some valid combination of MFT_ constants. It indicates the kinds of menu information to be shown. Our example uses only the MFT_STRING and MFT_SEPARATOR types. The MFT_SEPARATOR type produces the bar between the Help... and About... menu items.
    The item state field contains some valid combination of MFS_ constants. We use only the MFS_ENABLED type. It indicates a display or selection state.
    The item ID is the ID number sent via the WM_COMMAND message to the window procedure that has the menu. Although the field is 32-bits, only 16-bits are sent via WM_COMMAND. The convention is to name them as constants beginning with IDM_.
    The next field is the menu structure info. It is a byte field padded to a WORD boundary. Intel's little-endian number format allows us to set it and the padding with a DW directive. The API does not have standard names for the valid data of this field. We define MFR_ constants for this purpose. There are only two: the MFR_POPUP flag signals that the items following it are part of a popup submenu. The last item in the main menu or submenu has the MFR_END flag in this field. The two flags can be combined.
    The item text is the text that will be displayed by a MFT_STRING item. In spite of the fact that we use the "A" version of LoadMenuIndirect, the strings in the template must be in wide Unicode format. That's why DW is used instead of DB. As in C, strings are terminated with a zero null (NUL) character. This field always exist because of the need for padding to a DWORD boundary. An '&' in the item string causes the character following it to show up underlined. The underlined character is the keyboard menu selector. So if you press and release the Alt key, the first item in the menu bar will be highlighted. Then press the 'F' key, and the File submenu will pop up. If a submenu pops up, another keypress can select an item on the submenu. To display '&' as a character, you must double it as '&', '&'.
    And finally the F1 help ID. This field must exist for a MFR_POPUP entry; it must not exist for any other. It must be aligned on a DWORD boundary.
;
; Menu item (command) IDs
;
IDM_EXIT equ 101
IDM_HELP equ 901
IDM_ABOUT equ 902
;
; Menu template
;
MFR_END    equ 80h
MFR_POPUP  equ 01h

        .data
        align   4       ; must align on DWORD boundary
appMenuTemplate dw 1    ; menu template version
                dw 4    ; offset from end of this word to menu item list
                dd 0    ; menu bar help ID

                dd MFT_STRING,MFS_ENABLED,0
                dw MFR_POPUP                    ; first column
                dw '&','F','i','l','e',0,0      ; pad to align 4
                dd 0                            ; popup help ID

                dd MFT_STRING,MFS_ENABLED,IDM_EXIT
                dw MFR_END                      ; bottom of column
                dw 'E','&','x','i','t',0,0      ; pad to align 4

                dd MFT_STRING,MFS_ENABLED,0
                dw MFR_POPUP or MFR_END         ; second column, last one
                dw '&','H','e','l','p',0,0      ; pad to align 4
                dd 0                            ; popup help ID

                dd MFT_STRING,MFS_ENABLED,IDM_HELP
                dw 0
                dw '&','H','e','l','p','.','.','.',0

                dd MFT_SEPARATOR,0,0
                dw 0
                dw 0    ; pad to align 4

                dd MFT_STRING,MFS_ENABLED,IDM_ABOUT
                dw MFR_END                      ; bottom of column
                dw '&','A','b','o','u','t','.','.','.',0,0

Menu dispatch

When a menu item is selected and "activated", Windows sends a WM_COMMAND message to the window containing the menu. The menu item ID functions as the command ID passed to the window procedure in the lower sixteen bits of wParam.
        .code
WndProc:
        mov     eax,[esp+4+4]   ; message ID
        cmp     eax,WM_COMMAND  ; from menu, accelerator, or control
        je      execute_command
        cmp     eax,WM_DESTROY  ; window will be destroyed
        je      start_destroy
        jmp     DefWindowProc   ; delegate other message processing

;
; miscellaneous string data
;
        .data
helpCaption equ def_title
helpText db 'Help not implemented.',0

aboutCaption db 'About Win32 Assembly',0
aboutText db 'Place your copyright here.',0

        .code
execute_command:
        mov     eax,[esp+4+8]    ; wParam
        and     eax,large 0FFFFh ; command ID in low bits
        cmp     eax,IDM_EXIT
        je      exit_command
        cmp     eax,IDM_HELP
        je      help_command
        cmp     eax,IDM_ABOUT
        je      about_command
        xor     eax,eax          ; ignore other commands
        ret     16

Menu actions

For terminating the program, we post a WM_CLOSE message to the "main" window. This will cause a call to DestroyWindow, by default, and send a WM_DESTROY message to our window procedure. The WM_DESTROY code is not shown here. [See the minimal GUI program.]

We use MessageBox to display messages when the non-exit menu items are selected.

        extrn   PostMessage:near
        extrn   MessageBox:near
        extrn   hwndMain:dword  ; defined in winmain.asm

        .code
exit_command:
        ; trigger the main window close function
        push    large 0         ; lParam
        push    large 0         ; wParam
        push    large WM_CLOSE  ; msgid
        push    [hwndMain]      ; hwnd
        call    PostMessage

        xor     eax,eax
        ret     16

help_command:
        mov     eax,[esp+4+0]   ; hWnd
        push    large MB_OK
        push    offset helpCaption
        push    offset helpText
        push    eax             ; owner window
        call    MessageBox

        xor     eax,eax
        ret     16

about_command:
        mov     eax,[esp+4+0]    ; hWnd
        push    large MB_OK
        push    offset aboutCaption
        push    offset aboutText
        push    eax              ; owner window
        call    MessageBox

        xor     eax,eax
        ret     16